# -*- coding: utf-8 -*-
from random import randint


class GameState:
    def __init__(self, players, hat, game_round, phase, rounds,
                 current_added_player, settings, game_type=None):
        """make GameState with parameters

        args:
            players: (Players)
            hat: (Hat)
            game_round: (GameRound)
            phase: (int) from Phase
            rounds: (list of GameRound)
            current_added_player: (Player)
            settings: (Settings)
            game_type: (GameType)

        returns: (GameState)
        """
        self.players = players
        self.is_paused = False
        self.hat = hat
        self.game_round = game_round
        self.phase = phase
        self.rounds = rounds
        self.current_added_player = current_added_player
        self.settings = settings
        self.remote_id = None
        self.pin_code = None
        self.is_joining = False
        self.circle_num = 0  # number of circles played
        self.is_game_host = False
        self.game_type = game_type
        self.is_pressed_pause = False

    def __eq__(self, other):
        if self.players != other.players:
            return False
        if self.is_paused != other.is_paused:
            return False
        if self.hat != other.hat:
            return False
        if self.game_round != other.game_round:
            return False
        if self.phase != other.phase:
            return False
        if self.rounds != other.rounds:
            return False
        if self.current_added_player != other.current_added_player:
            return False
        if self.settings != other.settings:
            return False
        if self.remote_id != other.remote_id:
            return False
        if self.pin_code != other.pin_code:
            return False
        if self.is_joining != other.is_joining:
            return False
        if self.circle_num != other.circle_num:
            return False
        if self.game_type != other.game_type:
            return False
        return True

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        return ' '.join([str(self.players), str(self.is_paused),
                         str(self.hat), str(self.game_round),
                         str(self.phase), str(self.rounds),
                         str(self.current_added_player),
                         str(self.settings), str(self.remote_id),
                         str(self.is_joining),
                         str(self.pin_code), str(self.circle_num),
                         str(self.game_type),
                         str(self.game_type)]).encode('utf-8')

    def __str__(self):
        return self.__repr__()

    def clear(self):
        self.players = None
        self.hat = None
        self.game_round = None
        self.phase = None
        self.rounds = None
        self.current_added_player = None
        self.settings = None
        self.remote_id = None
        self.pin_code = None
        self.circle_num = 0
        self.game_type = None


class Phase:
    """phase of game
    """
    WELCOME, START_GAME, ADD_PLAYER, PLAYERS_ORDER, ADD_WORDS, COUNTDOWN,\
        FROM_TO, GUESS_WORD, RESULTS_OF_ONE_PAIR, GAME_CONFIG,\
        QUICK_GAME_SETTINGS, GAME_RESULTS, RULES, SYS_SETTINGS, RESULTS,\
        CURRES, LOAD_GAME, EDIT_PLAYERS, JOIN, ABOUT, NEXT_ROUND = range(21)


class GameRound:
    def __init__(self, time_left_sec, words, current_word, players_pair,
                 is_replayed=False):
        """make GameRound with parameters

        args:
            time_in_sec: (int)
            words: (list of WordGuessResult)
            current_word: (Word)
            players_pair: (pair of players)

        returns: (GameRound)
        """
        self.time_left_sec = time_left_sec
        self.words = words
        self.current_word = current_word
        self.players_pair = players_pair
        self.is_replayed = is_replayed

    def __eq__(self, other):
        return (self.time_left_sec == other.time_left_sec) and \
               (self.words == other.words) and \
               (self.current_word == other.current_word) and \
               (self.players_pair == other.players_pair) and \
               (self.is_replayed == other.is_replayed)

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        return ' '.join([str(self.time_left_sec), str(self.words),
                         str(self.current_word), str(self.players_pair),
                         str(self.is_replayed)]).encode('utf-8')

    def __str__(self):
        return self.__repr__()


class NextPairStrategy:
    """strategy to make next pair of players

       It is a parent class for classes TraditionalStrategy and OddStrategy.

    """

    def __init__(self, list_of_players):
        """make NextPairStrategy with list of players
        """
        self.list_of_players = list_of_players
        self.index_1 = None
        self.index_2 = None
        self.is_players_deleted = False
        self.is_same_pair = False

    def clear_pair(self):
        self.index_1 = None
        self.index_2 = None
        self.is_players_deleted = False
        self.is_same_pair = False

    def __eq__(self, other):
        return (self.list_of_players == other.list_of_players and
                self.index_1 == other.index_1 and
                self.index_2 == other.index_2 and
                self.is_same_pair == other.is_same_pair and
                self.is_players_deleted == other.is_players_deleted)

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        return ' '.join([str(self.list_of_players), str(self.index_1),
                         str(self.index_2), str(self.is_players_deleted),
                         str(self.is_same_pair)]).encode('utf-8')

    def __str__(self):
        return self.__repr__()


class TraditionalStrategy(NextPairStrategy):
    """
    >>> array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    >>> strategy = TraditionalStrategy(array)
    >>> b = array[-1]
    >>> ok = 0
    >>> for i in range(1000):
    ...     a = strategy.get_next_pair()
    ...     if a[0] != (b + 1) % len(array):
    ...         ok = 1
    ...         print('Error in', i)
    ...     b = a[0]
    >>> ok
    0
    """
    def get_next_pair(self):
        """get next pair of players

        args: None

        returns: (pair of Players)
        """
        if self.index_1 is None or self.index_2 is None:
            self.index_1 = 0
            self.index_2 = (len(self.list_of_players)) / 2
        elif self.is_players_deleted:
            self.index_1 %= len(self.list_of_players)
            self.index_2 = (self.index_1 + len(self.list_of_players) / 2) % \
                len(self.list_of_players)
        elif not self.is_same_pair:
            self.index_1 = (self.index_1 + 1) % len(self.list_of_players)
            self.index_2 = (self.index_2 + 1) % len(self.list_of_players)
        self.is_players_deleted, self.is_same_pair = False, False
        return (self.list_of_players[self.index_1],
                self.list_of_players[self.index_2])

    def get_pairs(self):
        """make list of all pairs
        returns: (list of pair of Players)
        """
        pairs = []
        for i in range(len(self.list_of_players)):
            pairs.append(self.get_next_pair())
        return pairs

    def is_pair_strategy(self):
        return True


class OddStrategy(NextPairStrategy):
    """
    >>> strategy = OddStrategy([0, 1, 2, 3, 4])
    >>> strategy.get_next_pair()
    (0, 1)
    >>> strategy.get_next_pair()
    (1, 2)
    >>> strategy.get_next_pair()
    (2, 3)
    >>> strategy.get_next_pair()
    (3, 4)
    >>> strategy.get_next_pair()
    (4, 0)
    >>> strategy.get_next_pair()
    (0, 2)
    >>> strategy.get_next_pair()
    (1, 3)
    >>> strategy.get_next_pair()
    (2, 4)
    >>> strategy.get_next_pair()
    (3, 0)
    >>> strategy.get_next_pair()
    (4, 1)
    >>> strategy.get_next_pair()
    (0, 3)
    """

    def get_next_pair(self):
        if self.index_1 is None or self.index_2 is None:
            self.index_1 = 0
            self.index_2 = 1
        elif self.is_players_deleted:
            self.index_1 %= len(self.list_of_players)
            self.index_2 %= len(self.list_of_players)
        elif not self.is_same_pair:
            self.index_1 = (self.index_1 + 1) % len(self.list_of_players)
            self.index_2 = (self.index_2 + 1) % len(self.list_of_players)
            if self.index_1 == 0:
                self.index_2 = (self.index_2 + 1) % len(self.list_of_players)
        if self.index_1 == self.index_2 == 0:
            self.index_2 = 1
        self.is_same_pair, self.is_players_deleted = False, False
        return (self.list_of_players[self.index_1],
                self.list_of_players[self.index_2])

    def is_pair_strategy(self):
        return False


class Players:
    """players
    >>> vasa = Player('Vasa', None)
    >>> dasha = Player('Dasha', None)
    >>> sasha = Player('Sasha', None)
    >>> Current_class = Players([vasa])
    >>> Current_class.add(dasha)
    >>> [player.name for player in Current_class.players]
    ['Vasa', 'Dasha']
    >>> Current_class.add(sasha)
    >>> [player.name for player in Current_class.players]
    ['Vasa', 'Dasha', 'Sasha']
    """

    def __init__(self, players, players_select_strategy=None):
        """make Players with list of players

        args:
            players: (list of Player)
        """
        self.players = players
        self.players_select_strategy = players_select_strategy

    def add(self, player):
        """add player to list of players

        args:
            player: (Player)
        """
        if not isinstance(player, Player):
            raise TypeError()
        self.players.append(player)

    def set_next_pair_strategy(self, players_select_strategy):
        """set stratagy

        args:
            players_select_strategy: (NextPairStrategy)
        """
        self.players_select_strategy = players_select_strategy

    def __eq__(self, other):
        return (self.players == other.players) and \
               (self.players_select_strategy == other.players_select_strategy)

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        return ' '.join([str(self.players),
                         str(self.players_select_strategy)]).encode('utf-8')

    def __str__(self):
        return self.__repr__()


class Hat:
    def __init__(self, words):
        """ constrcts hat of words
        words: (list of Word)

        Tests:
        >>> hat = Hat([Word('Petya', 'Submit')])
        >>> word = hat.next_word()
        >>> word.owner
        'Petya'
        >>> word.text
        'Submit'
        >>> hat.add_word(Word('Kostya', 'Hat'))
        >>> hat.add_word(Word('Petya', 'Hat'))
        >>> word = hat.next_word()
        >>> word.text
        'Hat'
        >>> word = hat.next_word()
        >>> word.text
        'Hat'
        """
        self.words = words

    def add_word(self, word):
        """add word to hat

        args:
            word: (Word)
        """
        if not isinstance(word, Word):
            raise TypeError()
        self.words.append(word)

    def next_word(self):
        """pop random word from hat
        """
        return self.words.pop(randint(0, len(self.words) - 1))

    def __eq__(self, other):
        return self.words == other.words

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        return str(self.words).encode('utf-8')

    def __str__(self):
        return self.__repr__()


class Player:
    def __init__(self, name, words):
        """

        args:
            name: (String)
            words: (list of Word)
        """
        self.name = name
        self.words = words
        self.finished_entering = False

    def add_word(self, word):
        """

        args:
            word: (Word)
        """
        self.words.append(word)

    def __eq__(self, other_player):
        if (self is None) != (other_player is None):
            return False
        return (self.name == other_player.name and
                self.words == other_player.words and
                self.finished_entering == other_player.finished_entering)

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        return ' '.join([str(self.name), str(self.words),
                         str(self.finished_entering)]).encode('utf-8')

    def __str__(self):
        return self.__repr__()


class Word:
    def __init__(self, owner, text):
        """

        args:
        owner: (Player)
        text: (String)
        """
        self.owner = owner
        self.text = text

    def __eq__(self, other):
        return (self.owner.name == other.owner.name) and \
               (self.text == other.text)

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        return (self.owner.name + ' ' + self.text).encode('utf-8')

    def __str__(self):
        return self.__repr__()


class Settings:
    def __init__(self, words_number=10,
                 time_per_round_sec=20,
                 max_round_num=None,
                 time_before_out_sec=3,
                 skip_words=0):
        """

        args:
            words_number: (int)
            time_per_sec: (int)
            max_round_num: (int)
            time_before_out_sec: (int)
            skip_words: (int)
        """
        self.words_number = words_number
        self.time_per_round_sec = time_per_round_sec
        self.max_round_num = max_round_num
        self.time_before_out_sec = time_before_out_sec
        self.skip_words = skip_words

    def __eq__(self, other):
        return (self.words_number == other.words_number and
                self.time_per_round_sec == other.time_per_round_sec and
                self.time_before_out_sec == other.time_before_out_sec and
                self.max_round_num == other.max_round_num and
                self.skip_words == other.skip_words)

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        return ' '.join([str(self.words_number),
                         str(self.time_per_round_sec),
                         str(self.max_round_num),
                         str(self.time_before_out_sec),
                         str(self.skip_words)]).encode('utf-8')

    def __str__(self):
        return self.__repr__()


class WordGuessResult:
    GUESS, FAIL, PUT_BACK, TIME_OUT, NOT_GUESSED = range(5)

    text_repr = {
        GUESS: 'GUESS',
        FAIL: 'FAIL',
        PUT_BACK: 'PUT_BACK',
        TIME_OUT: 'TIME_OUT'
    }

    def __init__(self, word, result, time):
        """

        args:
            word: (Word)
            result: (int)
            time: (float) time in sec from start of round
        """
        self.word = word
        self.result = result
        self.time = time

    def __eq__(self, other):
        return (self.word == other.word and self.result == other.result and
                self.time == other.time)

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        return ' '.join([str(self.word), str(self.result),
                         str(self.time)]).encode('utf-8')

    def __str__(self):
        return self.__repr__()


class GameTypes:
    NUM_TYPES = 3
    HAT_STANDARD, CROCODILE, ONE_WORD = range(NUM_TYPES)

    text_repr = {
        HAT_STANDARD: 'HAT_STANDARD',
        CROCODILE: 'CROCODILE',
        ONE_WORD: 'ONE_WORD'
    }


class GameType:
    def __init__(self, type):
        """

        args:
            type: (int)
        """
        if type is None:
            type = GameTypes.HAT_STANDARD
        if type not in range(GameTypes.NUM_TYPES):
            raise Exception('GameType: type index '+str(type)+' out of range')
        self.type = type

    def __eq__(self, other):
        return (self.type == other.type)

    def __ne__(self, other):
        return not (self == other)

    def __repr__(self):
        if self.type not in range(GameTypes.NUM_TYPES):
            raise Exception('GameType: index ' + str(self.type) +
                            ' out of type range')
        return GameTypes.text_repr[self.type].encode('utf-8')

    def __str__(self):
        return self.__repr__()


if __name__ == '__main__':
    import doctest

    doctest.testmod()
